#include <graphics/Atlas.h>
#include <graphics/Engine.h>
#include <system/Log.h>
#include <TinyXML.h>

namespace ne
{
    Atlas::Atlas(ResourceManager *pManager,
                 const uint64_t id,
                 const std::string &name,
                 const std::string &fileName,
                 const bool isManual,
                 ManualResourceLoader *pLoader)
        : Resource(pManager, id, name, fileName, isManual, pLoader)
          
    {
    }

    void Atlas::setTexture(const TexturePtr &pTexture)
    {
        mpTexture = pTexture;
    }

    const TexturePtr& Atlas::getTexture() const
    {
        return mpTexture;
    }

    uint32_t Atlas::getWidth() const
    {
        if (mpTexture.notNull())
        {
            return mpTexture->getWidth();
        }
        return 1;
    }

    uint32_t Atlas::getHeight() const
    {
        if (mpTexture.notNull())
        {
            return mpTexture->getHeight();
        }
        return 1;
    }

    bool Atlas::genSpriteGeometry(const std::string &name, const DynamicGeometryPtr &pGeometry,
                                  const Vector3 &pos, const Vector3 &xAxis, const Vector3 &yAxis,
                                  const Vector2 &size, const Color &color)
    {
        if (pGeometry.notNull() && !name.empty())
        {
            Sprite *pSprite = getSprite(name);
            if (pSprite)
            {
                switch (pSprite->getSpriteType())
                {
                    case SPT_COMMON:
                    {
                        return genCommonSpriteGeometry(pSprite, pGeometry, pos, xAxis, yAxis, size, color);
                    }

                    case SPT_SLICED:
                    {
                        return genSlicedSpriteGeometry(pSprite, pGeometry, pos, xAxis, yAxis, size, color);
                    }

                    default: break;
                }
            }
        }
        return false;
    }

    bool Atlas::genCommonSpriteGeometry(const Sprite *pSprite, const DynamicGeometryPtr &pGeometry,
                                        const Vector3 &pos, const Vector3 &xAxis, const Vector3 &yAxis,
                                        const Vector2 &size, const Color &color)
    {
        if (pSprite && pGeometry.notNull())
        {
            /** TODO: Face
                1----3
                |\   |
                | \  |
                |  \ |
                |   \|
                0----2
            */
            uint32_t index = pGeometry->getVertexCount();
            pGeometry->addPosition(pos);
            pGeometry->addPosition(pos + yAxis * size.y);
            pGeometry->addPosition(pos + xAxis * size.x);
            pGeometry->addPosition(pos + xAxis * size.x + yAxis * size.y);

            pGeometry->addColor(color);
            pGeometry->addColor(color);
            pGeometry->addColor(color);
            pGeometry->addColor(color);

            pGeometry->addTexCoord(pSprite->texCoords[0]);
            pGeometry->addTexCoord(ne::Vector2(pSprite->texCoords[0].x, pSprite->texCoords[1].y));
            pGeometry->addTexCoord(ne::Vector2(pSprite->texCoords[1].x, pSprite->texCoords[0].y));
            pGeometry->addTexCoord(pSprite->texCoords[1]);

            pGeometry->addIndex(index);
            pGeometry->addIndex(index + 2);
            pGeometry->addIndex(index + 1);
            pGeometry->addIndex(index + 3);
            pGeometry->addIndex(index + 1);
            pGeometry->addIndex(index + 2);
            return true;
        }
        return false;
    }

    bool Atlas::genSlicedSpriteGeometry(const Sprite *pSprite, const DynamicGeometryPtr &pGeometry,
                                        const Vector3 &pos, const Vector3 &xAxis, const Vector3 &yAxis,
                                        const Vector2 &size, const Color &color)
    {
        if (pSprite && pGeometry.notNull())
        {
            Vector2 sT(getWidth(), getHeight());
            Vector2 sS((pSprite->texCoords[1] - pSprite->texCoords[0]) * sT);
            if (sS.x < size.x && sS.y < size.y)
            {
                /** TODO: Face
                    ^
                    |
                    3-7-11-15
                    |\| \| \|
                    2-6-10-14
                    |\| \| \|
                    1-5--9-13
                    |\| \| \|
                    0-4--8-12-->
                */
                Vector2 sS0((pSprite->slicedTexCoords[0] - pSprite->texCoords[0]) * sT);
                Vector2 sS1((pSprite->texCoords[1] - pSprite->slicedTexCoords[1]) * sT);
                Vector2 offset[4];
                offset[0] = Vector2::ZERO;
                offset[1] = sS0;
                offset[2] = size - sS1;
                offset[3] = size;

                Vector2 uv[4];
                uv[0] = pSprite->texCoords[0];
                uv[1] = pSprite->slicedTexCoords[0];
                uv[2] = pSprite->slicedTexCoords[1];
                uv[3] = pSprite->texCoords[1];

                uint32_t index = pGeometry->getVertexCount();
                for (size_t x=0; x<4; ++x)
                {
                    for (size_t y=0; y<4; ++y)
                    {
                        pGeometry->addPosition(pos + xAxis * offset[x].x + yAxis * offset[y].y);
                        pGeometry->addColor(color);
                        pGeometry->addTexCoord(Vector2(uv[x].x, uv[y].y));
                    }
                }

                for (uint32_t x=0; x<3; ++x)
                {
                    for (uint32_t y=0; y<3; ++y)
                    {
                        uint32_t i = index + 4 * x + y;
                        pGeometry->addIndex(i);
                        pGeometry->addIndex(i + 4);
                        pGeometry->addIndex(i + 1);
                        pGeometry->addIndex(i + 5);
                        pGeometry->addIndex(i + 1);
                        pGeometry->addIndex(i + 4);
                    }
                }
                return true;
            }
            else if (sS.x < size.x)
            {
                /** TODO: Face
                    ^
                    |
                    1-3-5-7
                    |\|\|\|
                    0-2-4-6-->
                */
                Vector2 offset[4];
                offset[0] = Vector2::ZERO;
                offset[1].x = (pSprite->slicedTexCoords[0].x - pSprite->texCoords[0].x) * sT.x;
                offset[1].y = size.y;
                offset[2].x = size.x - (pSprite->texCoords[1].x - pSprite->slicedTexCoords[1].x) * sT.x;
                offset[2].y = size.y;
                offset[3] = size;

                Vector2 uv[4];
                uv[0] = pSprite->texCoords[0];
                uv[1].x = pSprite->slicedTexCoords[0].x;
                uv[1].y = pSprite->texCoords[1].y;
                uv[2].x = pSprite->slicedTexCoords[1].x;
                uv[2].y = pSprite->texCoords[1].y;
                uv[3] = pSprite->texCoords[1];

                uint32_t index = pGeometry->getVertexCount();
                for (size_t x=0; x<4; ++x)
                {
                    for (size_t y=0; y<2; ++y)
                    {
                        pGeometry->addPosition(pos + xAxis * offset[x].x + yAxis * offset[y].y);
                        pGeometry->addColor(color);
                        pGeometry->addTexCoord(Vector2(uv[x].x, uv[y].y));
                    }
                }

                for (uint32_t x=0; x<3; ++x)
                {
                    uint32_t i = index + 2 * x;
                    pGeometry->addIndex(i);
                    pGeometry->addIndex(i + 2);
                    pGeometry->addIndex(i + 1);
                    pGeometry->addIndex(i + 3);
                    pGeometry->addIndex(i + 1);
                    pGeometry->addIndex(i + 2);
                }
                return true;
            }
            else if (sS.y < size.y)
            {
                /** TODO: Face
                    ^
                    |
                    3-7
                    |\|
                    2-6
                    |\|
                    1-5
                    |\|
                    0-4-->
                */
                Vector2 offset[4];
                offset[0] = Vector2::ZERO;
                offset[1].x = size.x;
                offset[1].y = (pSprite->slicedTexCoords[0].y - pSprite->texCoords[0].y) * sT.y;
                offset[2].x = size.x;
                offset[2].y = size.y - (pSprite->texCoords[1].y - pSprite->slicedTexCoords[1].y) * sT.y;
                offset[3] = size;

                Vector2 uv[4];
                uv[0] = pSprite->texCoords[0];
                uv[1].x = pSprite->texCoords[1].x;
                uv[1].y = pSprite->slicedTexCoords[0].y;
                uv[2].x = pSprite->texCoords[1].x;
                uv[2].y = pSprite->slicedTexCoords[1].y;
                uv[3] = pSprite->texCoords[1];

                uint32_t index = pGeometry->getVertexCount();
                for (size_t x=0; x<2; ++x)
                {
                    for (size_t y=0; y<4; ++y)
                    {
                        pGeometry->addPosition(pos + xAxis * offset[x].x + yAxis * offset[y].y);
                        pGeometry->addColor(color);
                        pGeometry->addTexCoord(Vector2(uv[x].x, uv[y].y));
                    }
                }

                for (uint32_t y=0; y<3; ++y)
                {
                    uint32_t i = index + y;
                    pGeometry->addIndex(i);
                    pGeometry->addIndex(i + 4);
                    pGeometry->addIndex(i + 1);
                    pGeometry->addIndex(i + 5);
                    pGeometry->addIndex(i + 1);
                    pGeometry->addIndex(i + 4);
                }
                return true;
            }

            return genCommonSpriteGeometry(pSprite, pGeometry, pos, xAxis, yAxis, size, color);
        }
        return false;
    }

    bool Atlas::hasSprite(const std::string &name) const
    {
        SpriteMapConstIterator it = mSpriteMap.find(name);
        if (it != mSpriteMap.end())
        {
            return true;
        }
        return false;
    }

    Sprite* Atlas::getSprite(const std::string &name) const
    {
        SpriteMapConstIterator it = mSpriteMap.find(name);
        if (it != mSpriteMap.end())
        {
            return it->second;
        }
        return 0;
    }

    Sprite* Atlas::createSprite(const std::string &name)
    {
        if (!hasSprite(name))
        {
            Sprite *pSprite = new Sprite(name);
            mSpriteMap[name] = pSprite;
            return pSprite;
        }
        return 0;
    }

    bool Atlas::destroySprite(const std::string &name)
    {
        SpriteMapIterator it = mSpriteMap.find(name);
        if (it != mSpriteMap.end())
        {
            delete it->second;
            mSpriteMap.erase(name);
            return true;
        }
        return false;
    }

    void Atlas::destroyAllSprites()
    {
        for (SpriteMapIterator it=mSpriteMap.begin(); it!=mSpriteMap.end(); ++it)
        {
            delete it->second;
        }
        mSpriteMap.clear();
    }

    Atlas::~Atlas()
    {
        unload();
    }

    bool Atlas::loadImplement()
    {
        std::string fileName = getFileName();
        FILE *file = fopen(fileName.c_str(), "rb");
        if (!file)
        {
            fileName = Engine::GetSingleton().getWorkSpace() + getFileName();
            file = fopen(fileName.c_str(), "rb");
            if (!file)
            {
                Log::Out(LL_WARNING,
                    "Open Atlas (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
                return false;
            }
        }
        fclose(file);

        XMLDocument doc(fileName.c_str());
        if (!doc.LoadFile(TIXML_ENCODING_UTF8))
        {
            Log::Out(LL_WARNING,
                "Load AtlasXML (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        XMLElement *eAtlas = doc.FirstChildElement("Atlas");
        if (!eAtlas)
        {
            Log::Out(LL_WARNING,
                "Load Atlas (%s) from file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }


        int result = 0;
        std::string textureName;
        result = eAtlas->QueryStringAttribute("texture", &textureName);
        uint32_t widthTexture = 1;
        result = eAtlas->QueryUnsignedAttribute("width", &widthTexture);
        uint32_t heightTexture = 1;
        result = eAtlas->QueryUnsignedAttribute("height", &heightTexture);

        XMLElement *eSprite = eAtlas->FirstChildElement("Sprite");
        while (eSprite)
        {
            std::string spriteName;
            result = eSprite->QueryStringAttribute("name", &spriteName);
            std::string spriteType;
            result = eSprite->QueryStringAttribute("type", &spriteType);
            SpriteType type = Sprite::GetSpriteType(spriteType);
            Sprite *pSprite = createSprite(spriteName);
            if (pSprite)
            {
                pSprite->setSpriteType(type);
                const char *pText = eSprite->GetText();
                char *pToken = strtok(const_cast<char*>(pText), " ,:;");
                switch (type)
                {
                    case SPT_COMMON:
                    {
                        uint32_t d = 0;
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[0].x = real(d) / widthTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[0].y = real(d) / heightTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[1].x = real(d) / widthTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[1].y = real(d) / heightTexture;
                        break;
                    }

                    case SPT_SLICED:
                    {
                        uint32_t d = 0;
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[0].x = real(d) / widthTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[0].y = real(d) / heightTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[1].x = real(d) / widthTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->texCoords[1].y = real(d) / heightTexture;
                        pToken = strtok(0, " ,:;");

                        sscanf(pToken, "%u", &d);
                        pSprite->slicedTexCoords[0].x = real(d) / widthTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->slicedTexCoords[0].y = real(d) / heightTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->slicedTexCoords[1].x = real(d) / widthTexture;
                        pToken = strtok(0, " ,:;");
                        sscanf(pToken, "%u", &d);
                        pSprite->slicedTexCoords[1].y = real(d) / heightTexture;
                        break;
                    }

                    default: break;
                }
            }
            eSprite = eSprite->NextSiblingElement("Sprite");
        }

        ResourceCreateOrRetrieveResult res = TextureManager::GetSingleton().createOrRetrieve(textureName, textureName);
        mpTexture = res.first;

        if (mpTexture.isNull())
        {
            Log::Out(LL_WARNING,
                "Create Texture for Atlas (%s) from file (%s) failed", getName().c_str(), textureName.c_str());
        }

        if (!mpTexture->isLoaded())
        {
            if (!mpTexture->load(isBackgroundThreadLoading()))
            {
                Log::Out(LL_WARNING,
                    "Load Texture for Atlas (%s) from file (%s) failed", getName().c_str(), textureName.c_str());
            }
        }

        return true;
    }

    bool Atlas::unloadImplement()
    {
        destroyAllSprites();
        return true;
    }

    bool Atlas::downloadImplement()
    {
        FILE *file = openFile("wb");
        if (!file)
        {
            Log::Out(LL_WARNING,
                "Create Atlas (%s) file (%s) failed", getName().c_str(), getFileName().c_str());
            return false;
        }

        closeFile(file);

        return true;
    }

    size_t Atlas::_calculateSize() const
    {
        //TODO: Here need to be fixed
        return mSize;
    }
}
